﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Interceptor.Core.Objects
{
	public class TypeDescription
	{
		public bool AlreadyDecorated { get; private set; }
		public Type TypeToFindMatches { get; private set; }
		public Type Definition { get { return Hierarchy[0]; } }
		public Type[] Hierarchy { get; private set; }
		public Type[] Interfaces { get; private set; }
		public Type[] GenericArguments { get; private set; }
		public Type GenericTypeDefinition { get; private set; }

		public bool IsInterface { get { return Hierarchy[0].IsInterface; } }
		public bool IsAbstract { get { return Hierarchy[0].IsAbstract; } }
		public bool ContainsParameters { get { return GenericArguments.Any(x => x.IsGenericParameter); } }
		public Type[] GenericParameters { get { return GenericArguments.Where(x => x.IsGenericParameter).ToArray(); } }
		
		public static TypeDescription GetDescription(object instance)
		{
			return new TypeDescription(instance);
		}
		private TypeDescription(object instance)
		{

			FillTypeDescription(instance);
		}

		private void FillTypeDescription(object instance)
		{
			var typeDefinition = instance.GetType();
			Hierarchy = GetTypeHierarchy(typeDefinition);
			Interfaces = GetTypeInterfaces(typeDefinition);
			GenericArguments = GetGenericArguments(typeDefinition);
			GenericTypeDefinition = GetGenericTypeDefinition(typeDefinition);
			AlreadyDecorated = Interfaces.Any(x => x == typeof (IDecorator));

			TypeToFindMatches = AlreadyDecorated
			                    	? ((IDecorator) instance).Target.GetType()
									: Definition;
		}

		private Type GetGenericTypeDefinition(Type typeDefinition)
		{
			return typeDefinition.IsGenericType
			       	? typeDefinition.GetGenericTypeDefinition()
			       	: null;
		}

		private Type[] GetGenericArguments(Type typeDefinition)
		{
			return (typeDefinition.IsGenericType)
			       	? typeDefinition.GetGenericArguments()
			       	: new Type[0];
		}

		private Type[] GetTypeInterfaces(Type typeDefinition)
		{
			return typeDefinition.IsInterface
			       	? new[] {typeDefinition}.Concat(typeDefinition.GetInterfaces()).ToArray()
			       	: typeDefinition.GetInterfaces();
		}
		protected static Type[] UnrollInterfaceHierarchy(Type i)
		{
			List<Type> result = new List<Type>();
			result.Add(i);
			result.AddRange(i.GetInterfaces());
			return result.ToArray();
		}
		private static Type[] GetTypeHierarchy(Type type)
		{
			var result = new List<Type> {type};
			var current = type;
			while (current != typeof(object))
			{
				current = current.BaseType;
				result.Add(current);
			}
			return result.ToArray();
		}

	}
}
